//
//  UIImageAdditions.m
//  Tint
//
//  Created by Wu Kevin on 9/28/12.
//
//

#import "UIImageAdditions.h"

@implementation UIImage (Tint)


#pragma mark - Stretchable image

- (UIImage *)stretchableImage
{
    CGFloat hSpacing = floor((self.size.width+1.0)/2.0)-1.0;
    hSpacing = MAX(0.0, hSpacing);
    CGFloat vSpacing = floor((self.size.height+1.0)/2.0)-1.0;
    vSpacing = MAX(0.0, vSpacing);
    
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(vSpacing, hSpacing, vSpacing, hSpacing);
    return [self stretchableImageWithCapInsets:edgeInsets];
}

- (UIImage *)stretchableImageWithCenterSize:(CGSize)centerSize
{
    CGFloat hSpacing = floor((self.size.width - centerSize.width)/2.0);
    CGFloat vSpacing = floor((self.size.height - centerSize.height)/2.0);
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(vSpacing, hSpacing, vSpacing, hSpacing);
    return [self stretchableImageWithCapInsets:edgeInsets];
}

- (UIImage *)stretchableImageWithCapInsets:(UIEdgeInsets)capInsets
{
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(floor(capInsets.top),
                                               floor(capInsets.left),
                                               floor(capInsets.bottom),
                                               floor(capInsets.right));
    
    CGFloat systemVersion = [[[[UIDevice class] currentDevice] systemVersion] floatValue];
    if ( systemVersion < 5.0 ) {
        return [self stretchableImageWithLeftCapWidth:edgeInsets.left topCapHeight:edgeInsets.top];
    }
    
    return [self resizableImageWithCapInsets:edgeInsets];
}


#pragma mark - Round corner

- (UIImage *)makeRoundCornersOfSize:(CGSize)cornerSize
{
    return [self makeRoundCornersOfSize:cornerSize usingMode:TTImageRoundCornerModeAll];
}

- (UIImage *)makeRoundCornersOfSize:(CGSize)cornerSize usingMode:(TTImageRoundCornerMode)mode
{
    if ((cornerSize.width <= 0.0) ||
        (cornerSize.height <= 0.0))
    {
        return self;
    }
	
    CGSize newCornerSize = CGSizeMake(floor(cornerSize.width), floor(cornerSize.height));
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 self.size.width,
                                                 self.size.height,
                                                 8,
                                                 4*self.size.width,
                                                 colorSpace,
                                                 kCGImageAlphaPremultipliedFirst);
	CGColorSpaceRelease(colorSpace);
    
    CGContextBeginPath(context);
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, 0.0, 0.0);
    CGContextScaleCTM (context, newCornerSize.width, newCornerSize.height);
    CGFloat fw = self.size.width / newCornerSize.width;
    CGFloat fh = self.size.height / newCornerSize.height;
    if ( mode == TTImageRoundCornerModeLeft ) {
        CGContextMoveToPoint(context, fw/2.0, fh);
        CGContextAddArcToPoint(context, 0.0, fh, 0.0, fh/2.0, 1.0);
        CGContextAddArcToPoint(context, 0.0, 0.0, fw/2.0, 0.0, 1.0);
        CGContextAddLineToPoint(context, fw, 0.0);
        CGContextAddLineToPoint(context, fw, fh);
        CGContextAddLineToPoint(context, fw/2.0, fh);
    } else if ( mode == TTImageRoundCornerModeTop ) {
        CGContextMoveToPoint(context, fw, fh/2.0);
        CGContextAddArcToPoint(context, fw, fh, fw/2.0, fh, 1.0);
        CGContextAddArcToPoint(context, 0.0, fh, 0.0, fh/2.0, 1.0);
        CGContextAddLineToPoint(context, 0.0, 0.0);
        CGContextAddLineToPoint(context, fw, 0.0);
        CGContextAddLineToPoint(context, fw, fh/2.0);
    } else if ( mode == TTImageRoundCornerModeRight ) {
        CGContextMoveToPoint(context, fw/2.0, 0.0);
        CGContextAddArcToPoint(context, fw, 0.0, fw, fh/2.0, 1.0);
        CGContextAddArcToPoint(context, fw, fh, fw/2.0, fh, 1.0);
        CGContextAddLineToPoint(context, 0.0, fh);
        CGContextAddLineToPoint(context, 0.0, 0.0);
        CGContextAddLineToPoint(context, fw/2.0, 0.0);
    } else if ( mode == TTImageRoundCornerModeBottom ) {
        CGContextMoveToPoint(context, 0.0, fh/2.0);
        CGContextAddArcToPoint(context, 0.0, 0.0, fw/2.0, 0.0, 1.0);
        CGContextAddArcToPoint(context, fw, 0.0, fw, fh/2.0, 1.0);
        CGContextAddLineToPoint(context, fw, fh);
        CGContextAddLineToPoint(context, 0.0, fh);
        CGContextAddLineToPoint(context, 0.0, fh/2.0);
    } else if ( mode == TTImageRoundCornerModeLeftTop ) {
        CGContextMoveToPoint(context, fw/2.0, fh);
        CGContextAddArcToPoint(context, 0.0, fh, 0.0, fh/2.0, 1.0);
        CGContextAddLineToPoint(context, 0.0, 0.0);
        CGContextAddLineToPoint(context, fw, 0.0);
        CGContextAddLineToPoint(context, fw, fh);
        CGContextAddLineToPoint(context, fw/2.0, fh);
    } else if ( mode == TTImageRoundCornerModeTopRight ) {
        CGContextMoveToPoint(context, fw, fh/2.0);
        CGContextAddArcToPoint(context, fw, fh, fw/2.0, fh, 1.0);
        CGContextAddLineToPoint(context, 0.0, fh);
        CGContextAddLineToPoint(context, 0.0, 0.0);
        CGContextAddLineToPoint(context, fw, 0.0);
        CGContextAddLineToPoint(context, fw, fh/2.0);
    } else if ( mode == TTImageRoundCornerModeRightBottom ) {
        CGContextMoveToPoint(context, fw/2.0, 0.0);
        CGContextAddArcToPoint(context, fw, 0.0, fw, fh/2.0, 1.0);
        CGContextAddLineToPoint(context, fw, fh);
        CGContextAddLineToPoint(context, 0.0, fh);
        CGContextAddLineToPoint(context, 0.0, 0.0);
        CGContextAddLineToPoint(context, fw/2.0, 0.0);
    } else if ( mode == TTImageRoundCornerModeBottomLeft ) {
        CGContextMoveToPoint(context, 0.0, fh/2.0);
        CGContextAddArcToPoint(context, 0.0, 0.0, fw/2.0, 0.0, 1.0);
        CGContextAddLineToPoint(context, fw, 0.0);
        CGContextAddLineToPoint(context, fw, fh);
        CGContextAddLineToPoint(context, 0.0, fh);
        CGContextAddLineToPoint(context, 0.0, fh/2.0);
    } else {
        CGContextMoveToPoint(context, fw, fh/2.0);
        CGContextAddArcToPoint(context, fw, fh, fw/2.0, fh, 1.0);
        CGContextAddArcToPoint(context, 0.0, fh, 0.0, fh/2.0, 1.0);
        CGContextAddArcToPoint(context, 0.0, 0.0, fw/2.0, 0.0, 1.0);
        CGContextAddArcToPoint(context, fw, 0.0, fw, fh/2.0, 1.0);
    }
    CGContextClosePath(context);
    CGContextRestoreGState(context);
    CGContextClip(context);
	
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, self.size.width, self.size.height), self.CGImage);
    CGImageRef maskedImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    UIImage *maskedImage = [UIImage imageWithCGImage:maskedImageRef];
    CGImageRelease(maskedImageRef);
	
    return maskedImage;
}



#pragma mark - Cropping

- (UIImage *)cropCenter
{
    CGFloat width = MIN(self.size.width, self.size.height);
    return [self cropToSize:CGSizeMake(width, width) usingMode:TTImageCropModeCenter];
}

- (UIImage *)cropToSize:(CGSize)newSize
{
	return [self cropToSize:newSize usingMode:TTImageCropModeTopLeft];
}

- (UIImage *)cropToSize:(CGSize)newSize usingMode:(TTImageCropMode)mode
{
	CGFloat x = 0.0;
    CGFloat y = 0.0;
    if ( mode == TTImageCropModeTopCenter ) {
        x = (self.size.width - newSize.width) * 0.5;
        y = 0.0;
    } else if ( mode == TTImageCropModeTopRight ) {
        x = self.size.width - newSize.width;
        y = 0.0;
    } else if ( mode == TTImageCropModeBottomLeft ) {
        x = 0.0;
        y = self.size.height - newSize.height;
    } else if ( mode == TTImageCropModeBottomCenter ) {
        x = newSize.width * 0.5;
        y = self.size.height - newSize.height;
    } else if ( mode == TTImageCropModeBottomRight ) {
        x = self.size.width - newSize.width;
        y = self.size.height - newSize.height;
    } else if ( mode == TTImageCropModeLeftCenter ) {
        x = 0.0;
        y = (self.size.height - newSize.height) * 0.5;
    } else if ( mode == TTImageCropModeRightCenter ) {
        x = self.size.width - newSize.width;
        y = (self.size.height - newSize.height) * 0.5;
    } else if ( mode == TTImageCropModeCenter ) {
        x = (self.size.width - newSize.width) * 0.5;
        y = (self.size.height - newSize.height) * 0.5;
    } else {
        x = 0.0;
        y = 0.0;
    }
    
    return [self cropAt:CGRectMake(x, y, newSize.width, newSize.height)];
}

- (UIImage *)cropAt:(CGRect)rect
{
    CGRect newRect = CGRectMake(floor(rect.origin.x),
                                floor(rect.origin.y),
                                floor(rect.size.width),
                                floor(rect.size.height));
    CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, newRect);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    return image;
}



#pragma mark - Scaling

- (UIImage *)scaleToFitSize:(CGSize)bounds
{
    CGFloat factor1 = bounds.width/self.size.width;
    CGFloat factor2 = bounds.height/self.size.height;
    CGFloat factor = MIN(factor1, factor2);
    return [self scaleByFactor:factor];
}

- (UIImage *)scaleByFactor:(CGFloat)factor
{
	CGFloat resultWidth = ceil(self.size.width * factor);
	CGFloat resultHeight = ceil(self.size.height * factor);
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	CGContextRef context = CGBitmapContextCreate(NULL,
                                                 resultWidth,
                                                 resultHeight,
                                                 8,
                                                 resultWidth*4,
                                                 colorSpace,
                                                 kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
	CGColorSpaceRelease(colorSpace);
	
	// Handle orientation
	if ( UIImageOrientationLeft == self.imageOrientation ) {
		CGContextRotateCTM(context, M_PI_2);
		CGContextTranslateCTM(context, 0.0, -resultHeight);
	} else if ( UIImageOrientationRight == self.imageOrientation ) {
		CGContextRotateCTM(context, -M_PI_2);
		CGContextTranslateCTM(context, -resultWidth, 0.0);
	} else if ( UIImageOrientationDown == self.imageOrientation ) {
		CGContextTranslateCTM(context, resultWidth, resultHeight);
		CGContextRotateCTM(context, -M_PI);
	}
    
	// Image quality
	CGContextSetShouldAntialias(context, true);
	CGContextSetAllowsAntialiasing(context, true);
	CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    
	// Draw the image in the bitmap context
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, resultWidth, resultHeight), self.CGImage);
	CGImageRef scaledImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
	UIImage *scaledImage = [UIImage imageWithCGImage:scaledImageRef];
	CGImageRelease(scaledImageRef);
	
	return scaledImage;
}

- (UIImage *)scaleToSize:(CGSize)newSize
{
    CGSize resultSize = CGSizeMake(floor(newSize.width), floor(newSize.height));
    UIGraphicsBeginImageContext(resultSize);
    [self drawInRect:CGRectMake(0.0, 0.0, resultSize.width, resultSize.height)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}



#pragma mark - Merging

- (UIImage *)mergeWithImage:(UIImage *)image
{
    return [self mergeWithImage:image atPoint:CGPointZero];
}

- (UIImage *)mergeAtCenterWithImage:(UIImage *)image
{
    CGFloat x = (self.size.width - image.size.width) / 2.0;
    CGFloat y = (self.size.height - image.size.height) / 2.0;
    return [self mergeWithImage:image atPoint:CGPointMake(x, y)];
}

- (UIImage *)mergeWithImage:(UIImage *)image atPoint:(CGPoint)point
{
    if ( image == nil ) {
        return self;
    }
    
    CGPoint newPoint = CGPointMake(floor(point.x), floor(point.y));
    
    UIGraphicsBeginImageContext(self.size);
    [self drawAtPoint:CGPointZero];
    [image drawAtPoint:newPoint];
    UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resultImage;
}


#pragma mark - Masking

- (UIImage *)maskWithImage:(UIImage *)image
{
    CGImageRef sourceRef = self.CGImage;
    
    CGImageRef maskRef = image.CGImage;
	CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(maskRef),
                                        CGImageGetHeight(maskRef),
                                        CGImageGetBitsPerComponent(maskRef),
                                        CGImageGetBitsPerPixel(maskRef),
                                        CGImageGetBytesPerRow(maskRef),
                                        CGImageGetDataProvider(maskRef), NULL, false);
	
    CGImageRef masked = NULL;
    
	// Add alpha channel for images that don't have one (ie GIF, JPEG, etc...). This however has a computational cost.
	if ( CGImageGetAlphaInfo(sourceRef) == kCGImageAlphaNone ) {
        size_t width = CGImageGetWidth(sourceRef);
        size_t height = CGImageGetHeight(sourceRef);
        
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     8,
                                                     0,
                                                     colorSpace,
                                                     kCGImageAlphaPremultipliedFirst);
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), sourceRef);
        CGImageRef newImageRef = CGBitmapContextCreateImage(context);
        CGContextRelease(context);
        
        masked = CGImageCreateWithMask(newImageRef, mask);
        CGImageRelease(newImageRef);
        CGColorSpaceRelease(colorSpace);
	} else {
        masked = CGImageCreateWithMask(sourceRef, mask);
    }
	
	CGImageRelease(mask);
	
	UIImage *resultImage = [UIImage imageWithCGImage:masked];
	CGImageRelease(masked);
	
	return resultImage;
}


#pragma mark - Filtering

- (UIImage *)sepia
{
	CGFloat originalWidth = self.size.width;
	CGFloat originalHeight = self.size.height;
	size_t bytesPerRow = originalWidth * 4;
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
	CGContextRef context = CGBitmapContextCreate(NULL,
                                                 originalWidth,
                                                 originalHeight,
                                                 8,
                                                 bytesPerRow,
                                                 colorSpace,
                                                 kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
	CGColorSpaceRelease(colorSpace);
    
	// Draw the image in the bitmap context
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, originalWidth, originalHeight), self.CGImage);
    
	/// Grab the image raw data
	UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
	if ( !data ) {
		CGContextRelease(context);
		return nil;
	}
    
	const size_t bitmapByteCount = bytesPerRow * originalHeight;
	for ( size_t i=0; i<bitmapByteCount; i+=4 ) {
		UInt8 r = data[i + 1];
		UInt8 g = data[i + 2];
		UInt8 b = data[i + 3];
        
		NSInteger newRed = (r * 0.393) + (g * 0.769) + (b * 0.189);
		NSInteger newGreen = (r * 0.349) + (g * 0.686) + (b * 0.168);
		NSInteger newBlue = (r * 0.272) + (g * 0.534) + (b * 0.131);
        
		if ( newRed > 255 ) newRed = 255;
		if ( newGreen > 255 ) newGreen = 255;
		if ( newBlue > 255 ) newBlue = 255;
        
		data[i + 1] = (UInt8)newRed;
		data[i + 2] = (UInt8)newGreen;
		data[i + 3] = (UInt8)newBlue;
	}
    
	CGImageRef sepiaImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
	UIImage *sepiaImage = [UIImage imageWithCGImage:sepiaImageRef];
	CGImageRelease(sepiaImageRef);
    
	return sepiaImage;
}

- (UIImage *)grayscale
{
	CGFloat originalWidth = self.size.width;
	CGFloat originalHeight = self.size.height;
    
	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
	CGContextRef context = CGBitmapContextCreate(NULL,
                                                 originalWidth,
                                                 originalHeight,
                                                 8,
                                                 3*originalWidth,
                                                 colorSpace,
                                                 kCGImageAlphaNone);
	CGColorSpaceRelease(colorSpace);
    
	// Image quality
	CGContextSetShouldAntialias(context, false);
	CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    
	// Draw the image in the bitmap context
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, originalWidth, originalHeight), self.CGImage);
    
	// Create an image object from the context
	CGImageRef grayscaleImageRef = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
	UIImage *grayscaleImage = [UIImage imageWithCGImage:grayscaleImageRef];
	CGImageRelease(grayscaleImageRef);
    
	return grayscaleImage;
}

// by http://www.sixtemia.com/journal/2010/06/23/uiimage-negative-color-effect/
- (UIImage *)negative
{
    UIGraphicsBeginImageContext(self.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetBlendMode(context, kCGBlendModeCopy);
    [self drawInRect:CGRectMake(0.0, 0.0, self.size.width, self.size.height)];
    CGContextSetBlendMode(context, kCGBlendModeDifference);
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextFillRect(context, CGRectMake(0.0, 0.0, self.size.width, self.size.height));
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result;
}


@end
